﻿using Mono.Cecil;

namespace MethodCaching.Weaving
{
    /// <summary>
    /// A class that is capable of weaving an assembly and 
    /// saves the result back to disc.
    /// </summary>
    public class AssemblyWeaver : IAssemblyWeaver
    {
        private ModuleDefinition _moduleDefinition;        
        private readonly ITypeWeaver _typeWeaver;
        private readonly ITypeSelector _typeSelector;

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyWeaver"/> class.
        /// </summary>
        /// <param name="typeWeaver">The <see cref="ITypeWeaver"/> instance that is responsible for type weaving.</param>
        /// <param name="typeSelector">The <see cref="ITypeSelector"/> that is responsible for selecting the types elegible for weaving.</param>
        public AssemblyWeaver(ITypeWeaver typeWeaver, ITypeSelector typeSelector)
        {
            Guard.IsNotNull(typeWeaver, "typeWeaver");
            Guard.IsNotNull(typeSelector, "typeSelector");
            _typeWeaver = typeWeaver;
            _typeSelector = typeSelector;
        }

        /// <summary>
        /// Weaves the assembly identified by the <paramref name="assemblyFileName"/>.
        /// </summary>
        /// <param name="assemblyFileName">The full path to the target assembly.</param>
        public void Weave(string assemblyFileName)
        {            
            ReadModule(assemblyFileName);        
            WeaveTypes();
            WriteModule(assemblyFileName);
        }


        private void ReadModule(string assemblyFileName)
        {
            Guard.IsNotNull(assemblyFileName, "assemblyFileName");
            var readerParameters = new ReaderParameters();
            readerParameters.ReadSymbols = true;
            _moduleDefinition = ModuleDefinition.ReadModule(assemblyFileName, readerParameters);
        }

        private void WeaveTypes()
        {
            foreach (var typeDefinition in _typeSelector.Select(_moduleDefinition))
                _typeWeaver.Weave(typeDefinition);
        }

        private void WriteModule(string assemblyFileName)
        {
            var writerParameters = new WriterParameters();
            writerParameters.WriteSymbols = true;
            _moduleDefinition.Write(assemblyFileName);
        }        
    }
}
